package com.duniptech.soa.simulation;

import java.io.StringWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.Map.Entry;

import javax.xml.transform.Result;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.duniptech.soa.modeling.Connection;
import com.duniptech.soa.service.SimulationService;
import com.duniptech.soa.service.SimulationServiceInterface;
import com.duniptech.soa.util.Util;


public class Coordinator {
	private String name;
	private Element xmlModel;
	private HashMap<String, SimulationServiceInterface> simulators;
	private HashSet<Connection> connections;
	private double tL;
	private double tN;
	
	public Coordinator(String name, Element xmlCoupled) throws Exception {
		this.name = name;
		this.xmlModel = xmlCoupled;
		
		simulators = new HashMap<String, SimulationServiceInterface>();
		connections = new HashSet<Connection>();

		NodeList xmlComponentList = xmlModel.getChildNodes();
		for(int i=0; i<xmlComponentList.getLength(); ++i) {
			Node xmlComponentAsNode = xmlComponentList.item(i);
			if(xmlComponentAsNode.getNodeType()==Node.ELEMENT_NODE) {
				
				Element xmlComponent = (Element)xmlComponentAsNode;
				String nodeName = xmlComponent.getNodeName();
				if(Util.debug) System.out.println("Processing node " + nodeName);
				if(nodeName.equals("coupled") || nodeName.equals("atomic")) {
					
					String nameComponent = xmlComponent.getAttribute("name");
					String hostComponent = xmlComponent.getAttribute("host");
					if(Util.debug) System.out.println("Creating simulator for atomic " + nameComponent);

					StringWriter writer = new StringWriter();  
					Result result = new StreamResult(writer);  
					TransformerFactory.newInstance().newTransformer().transform(new DOMSource(xmlComponent),result);
					String xmlComponentAsString = writer.toString();

					SimulationServiceInterface simulationService = new SimulationService(hostComponent + Util.SIMULATION_SERVICE);
					simulationService.newSimulator(xmlComponentAsString);
					simulators.put(nameComponent, simulationService);
				}
				
				else if(nodeName.equals("connection")) {
					String simulatorFrom = xmlComponent.getAttribute("atomicFrom");
					String portFrom = xmlComponent.getAttribute("portFrom");
					String simulatorTo = xmlComponent.getAttribute("atomicTo");
					String portTo = xmlComponent.getAttribute("portTo");
					Connection connection = new Connection(simulatorFrom, portFrom, simulatorTo, portTo);
					connections.add(connection);					
				}
			}			
		}
		
		initialize(0);
	}
	
	public void initialize(double t) throws Exception {
		Set<Entry<String, SimulationServiceInterface>> pairs = simulators.entrySet();
		for(Entry<String, SimulationServiceInterface> pair : pairs) {
			pair.getValue().initialize(pair.getKey(), t);
		}
		tL = t;
		tN = t + ta(t);
	}
	
	private double ta(double t) throws Exception { 
		double tn = Double.POSITIVE_INFINITY;
		double tnAux = Double.POSITIVE_INFINITY;
		Set<Entry<String, SimulationServiceInterface>> pairs = simulators.entrySet();
		for(Entry<String, SimulationServiceInterface> pair : pairs) {
			tnAux = pair.getValue().getTN(pair.getKey());
			if(tnAux < tn) tn = tnAux;
		}
		return tn - t;
	}
	
	private void lambda(double t) throws Exception {
		Set<Entry<String, SimulationServiceInterface>> pairs = simulators.entrySet();
		for(Entry<String, SimulationServiceInterface> pair : pairs) {
			pair.getValue().lambda(pair.getKey(), t);
		}
		propagateOutput();
	}

	private void propagateOutput() throws Exception {
		for(Connection connection : connections) {
			SimulationServiceInterface simulatorFrom = simulators.get(connection.getSimulatorFrom());
			String[] xmlValues = simulatorFrom.getOutput(connection.getSimulatorFrom(), connection.getPortFrom());
			
			SimulationServiceInterface simulatorTo = simulators.get(connection.getSimulatorTo());
			simulatorTo.receive(connection.getSimulatorTo(), connection.getSimulatorFrom(), connection.getPortFrom(), connection.getSimulatorTo(), connection.getPortTo(), xmlValues);
		}
	}

	private void deltfcn(double t) throws Exception {
		Set<Entry<String, SimulationServiceInterface>> pairs = simulators.entrySet();
		for(Entry<String, SimulationServiceInterface> pair : pairs) {
			pair.getValue().deltfcn(pair.getKey(), t);
		}
		tL = t;
		tN = tL + ta(t);
	}
	
	public String simulate(long numIterations) throws Exception {
		double t = tN;
		long counter;
		for(counter=1; counter<numIterations && t<Double.POSITIVE_INFINITY; counter++) {
			lambda(t);
			deltfcn(t);
			t = tN;
		}
		StringBuffer console = new StringBuffer();
		console.append(counter + " iterations.\n");
		// Cosole of simulators
		console.append("Simulators output:\n");
		int posAt = name.indexOf("@");
		String clientIp = name.substring(posAt+1, name.length());
		Set<Entry<String, SimulationServiceInterface>> pairs = simulators.entrySet();
		for(Entry<String, SimulationServiceInterface> pair : pairs) {
			String response = pair.getValue().getConsole(clientIp);
			console.append(response);
		}
		return console.toString();
	}
	
	public void exit() throws Exception {
		Set<Entry<String, SimulationServiceInterface>> pairs = simulators.entrySet();
		for(Entry<String, SimulationServiceInterface> pair : pairs) {
			pair.getValue().exit(pair.getKey());
		}
		simulators.clear();
		System.gc();
	}

	
}

